package at.fhooe.stefanirndorfer.sound;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import org.lwjgl.openal.AL10;
import org.lwjgl.openal.AL11;
import org.lwjgl.openal.EFX10;

import at.fhooe.stefanirndorfer.effects.Effect;
import at.fhooe.stefanirndorfer.effects.EffectObject;
import at.fhooe.stefanirndorfer.effects.Filter;
import at.fhooe.stefanirndorfer.effects.LowPassFilter;
import at.fhooe.stefanirndorfer.effects.Reverb;
import at.fhooe.stefanirndorfer.logging.Log;

public class OpenALSource{
// static
	
	
  /** Maximal count of sources. */
  protected static final int MAX_SOURCES = 64;

private static final float DEFAULT_REF_DIST = 100.0f;

  /** IntBuffer of all available sources. */
  private static IntBuffer sources;
  
  /** Array of the appended OpenALSources. */
  private static OpenALSource[] openALSources;


  /**
   * Set the reference distance, the rolloff factor and the maximal distance of the source.
   *
   * @param id the id of the source
   */
  private static void setSoundEnvironment(int id){
    AL10.alSourcef(id, AL10.AL_REFERENCE_DISTANCE, DEFAULT_REF_DIST);
    AL10.alSourcef(id, AL10.AL_ROLLOFF_FACTOR, 0.5f);
    AL10.alSourcef(id, AL10.AL_MAX_DISTANCE, 1000);
  }
  
  /**
   * updates source velocity if the passed player-object equals this player variable
   * @param sourcePos 
   */
  public static void updateSourceParameters(SoundPlayer sp, FloatBuffer velocity, FloatBuffer position){
	  
	  for (int i = 0; i < openALSources.length; i++) {
		OpenALSource openALsource = openALSources[i];
		if(openALsource.soundPlayer != null){
			 int state = openALsource.getPropi(AL10.AL_SOURCE_STATE);
		      if(state == AL10.AL_PLAYING || state == AL10.AL_PAUSED){
			//Log.info("OpenALSource", "Updating positions");
			if(openALsource.soundPlayer.getId().equals(sp.getId())){
				int id = openALsource.getId();
				//update parameters
				AL10.alSource (id, AL10.AL_VELOCITY, velocity);
				AL10.alSource (id, AL10.AL_POSITION, position);
				Log.info("OpenALSource", "Parameters updated");
			}			
		}
		}
	}
  }
  
  
  /**
   * Resets properties of the source.
   *
   * @param id the id of the source
   */
  public static void resetSource(int id){
    AL10.alSourcef(id, AL10.AL_GAIN, 1);
  }

  /**
   * Allocate all available sources.
   */
  public static void alGenSources(){
    // get all available sources
    for(int i = MAX_SOURCES; i > 0; i--){
      sources = OpenALUtil.createIntBuffer(i);
      AL10.alGenSources(sources);
      if(AL10.alGetError() == AL10.AL_NO_ERROR) break;
    }

    // create empty openALSources
    openALSources = new OpenALSource[sources.capacity()];
    for(int i = 0; i < openALSources.length; i++){
      openALSources[i] = new OpenALSource();
      setSoundEnvironment(sources.get(i));
      openALSources[i].winControl(sources.get(i));
    }

  }

  /**
   * Frees all allocated sources.
   */
  public static void alDeleteSources(){
	  Log.info("OpenALSource", "Deleting all OpenAL sources");
    // free all allocated sources
    AL10.alDeleteSources(sources);
  }

// non static
  /** The source id (name). */
  private int id;
  
  /** Do I have the control over the source? Can I play some waves now? */
  private boolean hasControl = false;
  
  /** SoundPlayer to set parameters while playing */
  private SoundPlayer soundPlayer;


  /**
   * Creates an openAL source.
   */
  public OpenALSource(){
	 soundPlayer = null;
  }
  

  /**
   * Return the id of the source.
   *
   * @return id of the source
   */
  public int getId(){
    return id;
  }

  /**
   * Try to get control over an unused source.
   *
   * @return true if we have now control over a source
   */
  public boolean bind(SoundPlayer sp){
    if(hasControl) return true;

    // search for non playing source
    for(int i = 0; i < openALSources.length; i++){
      OpenALSource openALSource = openALSources[i];
      int state = openALSource.getPropi(AL10.AL_SOURCE_STATE);
      if(state != AL10.AL_PLAYING && state != AL10.AL_PAUSED){
        // we've found an unused source
        int id = openALSource.getId();
        openALSource.loseControl();
        resetSource(id);
        this.winControl(id);
        openALSources[i].soundPlayer = sp;
        this.setPropf(AL10.AL_GAIN, 1);
        Log.info("OpenALSource", "use sound source " + i + " for source id " + id);
//        System.out.println(i + ": " + this);
        return true;
      }
    }

    System.out.println("no sound source available");
    return false;
  }

  /**
   * The openALSource get control over an source.
   *
   * @param id the new id (name)
   */
  private void winControl(int id){
    this.id = id;
    hasControl = true;
  }

  /**
   * The openALSource lose it's control over the source.
   */
  protected void loseControl(){
    hasControl = false;
  }

  /**
   * Returns true if the openALSource has the control over a source.
   *
   * @return true if the openALSource has the control over a source
   */
  public boolean hasControl(){
    return hasControl;
  }

  public int getPropi(int prop){
    return AL10.alGetSourcei(id, prop);
  }

  public float getPropf(int prop){
    return AL10.alGetSourcef(id, prop);
  }

  public void setPropi(int prop, int value){
    AL10.alSourcei(id, prop, value);
  }

  public void setPropf(int prop, float value){
    AL10.alSourcef(id, prop, value);
  }

  public void setProp3f(int prop, float value1, float value2, float value3){
    AL10.alSource3f(id, prop, value1, value2, value3);
  }

  public void setPos(float x, float y, float z){
    AL10.alSource3f(id, AL10.AL_POSITION, x, y, z);
  }

  public void play(OpenALWave openALWave, boolean looping, FloatBuffer sourcePos, FloatBuffer sourceVel, SoundPlayer soundPlayer, Filter filter ){
	  play(openALWave, looping, sourcePos, sourceVel, soundPlayer, filter, null);
  }
  
  public void play(OpenALWave openALWave, boolean looping, FloatBuffer sourcePos, FloatBuffer sourceVel, SoundPlayer soundPlayer, Effect effect ){
	  play(openALWave, looping, sourcePos, sourceVel, soundPlayer, null, effect);
  
  }
  
  
  
  /**
   * Start playing the wave file.
   *
   * @param openALWave the wave file.
 * @param looping 
 * @param sourceVel 
 * @param sourcePos 
 * @param soundPlayer 
   */
  public void play(OpenALWave openALWave, boolean looping, FloatBuffer sourcePos, FloatBuffer sourceVel, SoundPlayer soundPlayer, Filter filter, Effect effect ){
	  if(bind(soundPlayer)){
		  stop();
		  //this.soundPlayer = soundPlayer;
		  EffectObject eo = null;
		  int filInt = 0;
		  // set up source input
		  AL10.alSourcei(id, AL10.AL_BUFFER, openALWave.getBuffer().get(0));
		  AL10.alSource (id, AL10.AL_POSITION, sourcePos);
		  AL10.alSource (id, AL10.AL_VELOCITY, sourceVel);

		  if(looping){
				AL10.alSourcei(id, AL10.AL_LOOPING,  AL10.AL_TRUE  );
			}else{
				AL10.alSourcei(id, AL10.AL_LOOPING,  AL10.AL_FALSE  );
			}
		  if(filter !=  null){
			  if(filter instanceof LowPassFilter){
				  filInt = processFilter(filter);
			  }
		  }
		  if (effect != null){
			  if(effect instanceof Reverb){
				  eo = processReverb(effect);
			  }
		  }
		 
		  OpenALUtil.checkError();
		  
		  // play source 0
		  AL10.alSourcePlay(id);
		  // System.out.println("source id " + id + " is playing sound " + openALWave);
		  OpenALUtil.checkError();
		  Log.info(this, "Sound played");
		  this.loseControl();
		  
		  if (effect != null){
			  // Cleanup
			  AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, EFX10.AL_EFFECTSLOT_NULL, 0,
					  EFX10.AL_FILTER_NULL);
			  EFX10.alAuxiliaryEffectSloti(eo.getEffectSlot(), EFX10.AL_EFFECTSLOT_EFFECT, EFX10.AL_EFFECT_NULL);
			  EFX10.alDeleteEffects(eo.getEffect());			  
		  }
	      if(filter != null){
	    	EFX10.alDeleteFilters(filInt);	    	  
	      }
	        
	  }
  }

private int processFilter(Filter filter) {
	LowPassFilter lpf = (LowPassFilter) filter;
	  // Add low-pass filter directly to source
	    final int f = EFX10.alGenFilters();
	    EFX10.alFilteri(f, EFX10.AL_FILTER_TYPE, EFX10.AL_FILTER_LOWPASS);
	    EFX10.alFilterf(f, EFX10.AL_AIR_ABSORPTION_FACTOR, 1.0f);
	    EFX10.alFilterf(f, EFX10.AL_LOWPASS_GAIN, lpf.getLowPass_Gain());
	    EFX10.alFilterf(f, EFX10.AL_LOWPASS_GAINHF, lpf.getHighFrequency_Gain());
	    AL10.alSourcei(id, EFX10.AL_DIRECT_FILTER, f);
	    return f;
}

private EffectObject processReverb(Effect effect) {
	//effect.useEffect(this);
	 Reverb r = (Reverb)effect;
	  // Add reverb effect
      final int effectSlot = EFX10.alGenAuxiliaryEffectSlots();
      final int reverbEffect = EFX10.alGenEffects();
      EFX10.alEffecti(reverbEffect, EFX10.AL_EFFECT_TYPE, EFX10.AL_EFFECT_REVERB);
      EFX10.alEffectf(reverbEffect, EFX10.AL_REVERB_DECAY_TIME, r.getDecayTime());
      EFX10.alAuxiliaryEffectSloti(effectSlot, EFX10.AL_EFFECTSLOT_EFFECT, reverbEffect);
      AL11.alSource3i(id, EFX10.AL_AUXILIARY_SEND_FILTER, effectSlot, 0,
	    EFX10.AL_FILTER_NULL);
      
      return new EffectObject (effectSlot, reverbEffect);
}

  public void pause(){
    AL10.alSourcePause(id);
  }

  public void stop(){
	  //this.soundPlayer = null;
    AL10.alSourceStop(id);
  }

  public void rewind(){
    AL10.alSourceRewind(id);
  }

  public void dispose(){
  }

  public String toString(){
    return "ID: " + id + ", HasControl: " + (hasControl ? "yes" : "no");
  }

public static float getReferenceDistance() {
	return DEFAULT_REF_DIST;
}


}
